/**
 * 
 */
package com.wgo.bpot.client.session;

import static com.wgo.bpot.common.transport.util.MethodNamingConvention.PROXY_SUPER_INTERFACE;
import static com.wgo.bpot.common.transport.util.MethodNamingConvention.REMOVE_METHOD;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Logger;

import com.wgo.bpot.common.transport.exception.ConventionsException;
import com.wgo.bpot.common.transport.servicefacade.DispatchEntry;
import com.wgo.bpot.common.transport.servicefacade.DispatchedModifyingMethodsTransport;
import com.wgo.bpot.common.transport.util.MethodNamingConvention;


/**
 * Arranges for server updates with fine granularity.
 * 
 * @author petterei
 * @version $Id: DispatchedModifyingMethodsCache.java,v 1.2 2006-01-27 08:37:02 petterei Exp $
 */
class DispatchedModifyingMethodsCache<T> {

	private static transient final Logger log = Logger.getLogger(DispatchedModifyingMethodsCache.class.getSimpleName());

	private Map<Method, Object> nonDependentMethods = new HashMap<Method, Object>();
	private Map<Method, T> singleDependentMethods = new HashMap<Method, T>(); // The argument of this method can be 'null', hence HashMap
	private Map<Method,Set<T>> negativeSingleDependentMethods = new Hashtable<Method,Set<T>>();
	private Map<Method,Set<T>> positiveSingleDependentMethods = new Hashtable<Method,Set<T>>();
	
	private T targetConcept = null;

	
	@SuppressWarnings("unchecked")
	private Map<Method,Collection<T>> getMultipleDependentMethods() {
		Map<Method,Set<T>> multipleDependentMethods = new Hashtable<Method,Set<T>>(negativeSingleDependentMethods.size() + positiveSingleDependentMethods.size());
		multipleDependentMethods.putAll(negativeSingleDependentMethods);
		multipleDependentMethods.putAll(positiveSingleDependentMethods);
		return (Map)multipleDependentMethods;
	}
	
	private void putNonDependantMethod(Method method, Object args) {
		nonDependentMethods.put(method, args);
	}
	
	private void putSingleDependantMethod(Method method, T parameter) {
		singleDependentMethods.put(method, parameter);
	}
	
	private void putPositiveSingleDependantMethod(Method positiveMethod, Method negativeMethod, T parameter) {
		putDependantMethod(positiveSingleDependentMethods, negativeSingleDependentMethods, positiveMethod, negativeMethod, parameter);
	}
	
	private void putNegativeDependantMethod(Method negativeMethod, Method positiveMethod, T parameter) {
		putDependantMethod(negativeSingleDependentMethods, positiveSingleDependentMethods, negativeMethod, positiveMethod, parameter);
	}
	
	private void putDependantMethod( Map<Method,Set<T>> dependentMethods,
			Map<Method,Set<T>> oppositeDependantMethods,
			Method method,
			Method oppositeMethod, T parameter) {
		Set<T> parameters = oppositeDependantMethods.get(oppositeMethod);
		if (null != parameters && parameters.remove(parameter)) {
			return;
		}
		parameters = dependentMethods.get(method);
		if (null != parameters) {
			parameters.add(parameter);
		} else {
			parameters = new HashSet<T>();
			parameters.add(parameter);
			dependentMethods.put(method, parameters);
		}
	}
	
	@SuppressWarnings("unchecked")
	protected void updateModifyingMethods(MethodNamingConvention methodType, Method actualMethod, Object[] args, Object result) {
		Class declaringInterface = actualMethod.getDeclaringClass();
		if (MethodNamingConvention.MODIFICATION_METHOD_TYPES.contains(methodType)) {
			Exception retriveMethodExcception = null;
			switch (methodType) {
			case INSERT_METHOD:
				throw new DomainSessionException(methodType.name() + " has not implemented functionality to add " + actualMethod.getName() + " (from " + declaringInterface.getClass().getSimpleName() + ") to " + getClass().getSimpleName() );
			case PUT_METHOD:
				if (args[0] instanceof DomainProxy) {
					// temporary, until maps are supported by the proxy:
					Set<T> arguments = positiveSingleDependentMethods.get(actualMethod);
					if (null == arguments) {
						arguments = new HashSet<T>();
						log.fine(actualMethod.getName() + ", value changed: " + methodType.hasValueChanged(actualMethod,result) + " Parameter 0: " + args[0] + " Result: " + result);
						positiveSingleDependentMethods.put(actualMethod, arguments);
					}
					arguments.add(((DomainProxy<T>)args[0]).getActualDomainModelObject());
					// temporary end  
					
//					String methodCoreName = actualMethod.getName().substring(methodType.getPrefix().length());
//					Method oppositeMethod = null ;
//					try {
//						oppositeMethod = MethodNamingConvention.retrieveMethod(MethodNamingConvention.REMOVE_METHOD.getPrefix(), methodCoreName, declaringInterface, args[0].getClass());
//					} catch (RematoException e) {} // ignore exception
//					if (null != oppositeMethod) {
//						putPositiveSingleDependantMethod(actualMethod, oppositeMethod, ((DomainProxy<T>)args[0]).getActualDomainModelObject());
//					} else {
//						putSingleDependantMethod(actualMethod, ((DomainProxy<T>)args[0]).getActualDomainModelObject());
//					}
				} else if (null != args[0]){
					log.fine(actualMethod.getName() + ", value changed: " + methodType.hasValueChanged(actualMethod,result) + " Parameter 0: " + args[0] + " Result: " + result);
					putNonDependantMethod(actualMethod, args[0]);
				} // ignore if parameter is 'null'
				break;
			case SET_METHOD:
				if (args[0] instanceof DomainProxy) {
					putSingleDependantMethod(actualMethod, ((DomainProxy<T>)args[0]).getActualDomainModelObject());
				} else if (null == args[0]) {
					if (PROXY_SUPER_INTERFACE.isAssignableFrom(actualMethod.getParameterTypes()[0])) {
						putSingleDependantMethod(actualMethod, null);
					} else {
						putNonDependantMethod(actualMethod, args[0]);
					}
				} else {
					putNonDependantMethod(actualMethod, args[0]);
				}
				break;
			case ADD_METHOD:
				if (args[0] instanceof DomainProxy) {
					Method oppositeMethod = null ;
					try {
						oppositeMethod = methodType.retrieveOppositeMethod(actualMethod);
					} catch (ConventionsException e) {
						retriveMethodExcception = e;
					}
					if (null != oppositeMethod) {
                        // Error here
						log.fine(actualMethod.getName() + ", value changed: " + methodType.hasValueChanged(actualMethod,result) + " Parameter 0: " + args[0] + " Result: " + result);
						if (methodType.hasValueChanged(actualMethod,result)) {
							putPositiveSingleDependantMethod(actualMethod, oppositeMethod, ((DomainProxy<T>)args[0]).getActualDomainModelObject());
						}
						break;
					} // no else, add method should have a remove method..
				}
			case REMOVE_METHOD:
				if (args[0] instanceof DomainProxy) {
					Method oppositeMethod = null;
					try {
						oppositeMethod = methodType.retrieveOppositeMethod(actualMethod);
					} catch (ConventionsException e) {
						retriveMethodExcception = e;
					}
					if (null != oppositeMethod) { // FIXME: what about non-dependent methods..?
						log.fine(actualMethod.getName() + ", value changed: " + methodType.hasValueChanged(actualMethod,result) + " Parameter 0: " + args[0] + " Result: " + result);
						if (methodType.hasValueChanged(actualMethod,result)) {
							putNegativeDependantMethod(actualMethod, oppositeMethod, ((DomainProxy<T>)args[0]).getActualDomainModelObject());
						}
						return;
					}
				}
			default:
				throw new DomainSessionException("Inconsistent method dispatched: " + actualMethod.getName() + "Arguments type: " + args[0].getClass() + "(Implementing interface: " + args[0].getClass().getInterfaces()[0] +")"  , retriveMethodExcception);
			}
			if (null != retriveMethodExcception) {
				log.warning("Opposite method was not retrieved for: " + actualMethod.getName() + ", declaring interface: " + declaringInterface + ", method type: " + methodType.name() );
			}
		} else {
			throw new DomainSessionException(methodType.name() + " is not a modification method-type, hence " + actualMethod.getName() + " from " + declaringInterface.getClass().getSimpleName() + " is not added to " + getClass().getSimpleName());
		}
	}
	protected void setTargetConcept(T targetConcept) {
		this.targetConcept = targetConcept;
	}
	/**
	 * Transforms the the cached information to a 
	 * DispatchedModifyingMethodsTransport which is compatible with hessian.
	 * 
	 * @return a transport object or 'null' if no methods are cached (to avoid 
	 * 	unecessary server calls).
	 */
	protected DispatchedModifyingMethodsTransport getTransport() {
		if (!isDirty()) {
			return null;
		}
		// necessary to bypass hessian incompability
		DispatchedModifyingMethodsTransport transport = new DispatchedModifyingMethodsTransport();
		ArrayList<DispatchEntry> nonDep = new ArrayList<DispatchEntry>();
		for (Entry<Method, Object> entry : nonDependentMethods.entrySet()) {
			DispatchEntry dEntry = new DispatchEntry();
			dEntry.setMethod(entry.getKey());
			dEntry.setArgument(entry.getValue());
			nonDep.add(dEntry);
		}
		transport.setNonDependentMethods(nonDep);
		ArrayList<DispatchEntry> singleDep = new ArrayList<DispatchEntry>();
		for (Entry<Method, T> entry : singleDependentMethods.entrySet()) {
			Method method = entry.getKey();
			if (null != method) {
				DispatchEntry dEntry = new DispatchEntry();
				dEntry.setMethod(method);
				dEntry.setArgument(entry.getValue());
				singleDep.add(dEntry);
			} else {
				log.warning("Key ( the method) of single dependent methods was 'null', argument: " + entry.getValue());
			}
		}
		transport.setSingleDependentMethods(singleDep);
		ArrayList<DispatchEntry> multipleDep = new ArrayList<DispatchEntry>();
		for (Entry<Method, Collection<T>> entry : getMultipleDependentMethods().entrySet()) {
			DispatchEntry dEntry = new DispatchEntry();
			dEntry.setMethod(entry.getKey());
			dEntry.setArgument(entry.getValue());
			multipleDep.add(dEntry);
		}
		transport.setMultipleDependentMethods(multipleDep);
		return transport;
	}
	
	private boolean isDirty() {
		if (null == targetConcept 
				&& nonDependentMethods.isEmpty() 
				&& singleDependentMethods.isEmpty() ) {
			if (! negativeSingleDependentMethods.isEmpty()) {
				for (Set set : negativeSingleDependentMethods.values()) {
					if (! set.isEmpty()) {
						return true;
					}
				}
			}
			if (! positiveSingleDependentMethods.isEmpty()) {
				for (Set set : positiveSingleDependentMethods.values()) {
					if (! set.isEmpty()) {
						return true;
					}
				}
			}
		} else {
			return true;
		}
		return false;
	}
}